#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "lzw/src/lzw.h"

//颜色表
uint32_t rainbowColor[] = {
    0XFF0000, // 赤
    0XFFA500, // 橙
    0XFFFF00, // 黄
    0X00FF00, // 绿
    0X007FFF, // 青
    0X0000FF, // 蓝
    0X8B00FF, // 紫
    0X000000  // 黑
};


int main(){
    FILE * gif_file = fopen("rainbow.gif", "wb+");

    //GIF 文件头， 6个字节的内容是 GIF 的署名和版本号  内容为: GIF89a
    uint8_t gif_header[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };
    fwrite(gif_header, 6, 1, gif_file);

    /*
     * 逻辑屏幕标识符（7个字节）
     * */
    uint16_t gif_width = 700; //屏幕逻辑宽度：定义了GIF 图像的像素宽度，大小为2字节
    uint16_t gif_height = 700; //屏幕逻辑高度：定义了GIF 图像的像素高度，大小为2字节
    uint8_t gif_logical_scrren_pack_byte = 0xF2; //打包值，一个字节
    /*
     * 0xF2 ==> 0b1 111 0 010
     * m = 1
     * cr = 111
     * s = 0
     * pixel = 010
     * */
    uint8_t gif_bg_clolor_index = 0; //背景颜色：背景颜色在全局颜色列表中的索引
    uint8_t gif_pixel_aspect = 0; //全局颜色列表大小，pixel+1 确定颜色列表的索引数 (2^(pixle+1))


    fputc(gif_width >> 0, gif_file);  //width low 8
    fputc(gif_width >> 8, gif_file); //width high 8
    fputc(gif_height >> 0, gif_file);
    fputc(gif_height >> 8, gif_file);
    fputc(gif_logical_scrren_pack_byte, gif_file);
    fputc(gif_bg_clolor_index, gif_file);
    fputc(gif_pixel_aspect, gif_file);



    //全局颜色表: 在逻辑屏幕标识之后，每个颜色索引由三个字节组成，按RGB 顺序排列
    printf("全局颜色表颜色数L:%lu\n", sizeof(rainbowColor) / sizeof(uint32_t));

    for(int i = 0; i < 8; i++) {
        //根据颜色索引取出颜色表中的颜色
        uint32_t color_rgb = rainbowColor[i];
        //取出每种颜色中的RGB分量
        uint8_t R = (color_rgb & 0xFF0000) >> 16; //取出R字节的分量
        uint8_t G = (color_rgb & 0x00FF00) >> 8;
        uint8_t B = (color_rgb & 0x0000FF) >> 0;

        fputc(R, gif_file);
        fputc(G, gif_file);
        fputc(B, gif_file);
    }

    //Application Extension //循环块扩展 必须立刻出现在逻辑屏幕描述符的全局颜色表之后,共由19个字节长
    //NETSAPE2.0
    uint8_t gif_application_Extension[] = {0x21, 0xFF, 0x0B, 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30, 0x03, 0x01, 0x00, 0x00, 0x00};    
    fwrite(gif_application_Extension, 19, 1, gif_file);

    //Comment Extension: 注释扩展
    //允许你将 ASCII 文件嵌入到 GIF 文件，有时被用来图像描述或其它人类可读的元数据，如图像捕获的GPS 定位
    /*
     * 标识符号:21 FE
     * 后面内容为： Created with ezgit.com GIF maker
     * */
    uint8_t gif_comment_extension[] = {0x21, 0xFE, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x65, 0x7A, 0x67, 0x69, 0x66, 0x2E, 0x63, 0x6F, 0x6D, 0x20, 0x47, 0x49, 0x46, 0x20, 0x6D, 0x61, 0x6B, 0x65, 0x72, 0x00};
    fwrite(gif_comment_extension, 36, 1, gif_file);

    for(int i = 0; i< 7; i++) {
        //图形控制扩展: 放在一个图像标识符的前面，用来控制它后面的第一个图像的显示,一共 8个字节长
        uint8_t gif_graphic_control_extension[] = {0x21, 0xF9, 0x04, 0x00, 0x32, 0x00, 0xFF, 0x00};
        fwrite(gif_graphic_control_extension, 8, 1, gif_file);

        //图像标识符： 10个字节长
        /*
         * 图像标识符开始标志位：0x2c
         * X 方向偏移量： 2个字节
         * Y 方向偏移量： 2个字节
         * 图像宽度：两个字节 
         * 图像高度：两个字节 
         * 局部标志：一个字节
             * 0 0 0 00 000
             * m i s r   pixel
             * m: 局部颜色表标志
             * i：交织标志
             * s：分类标志
             * r：保留，必须初始化为0
             * pixle：局部颜色表大小 //如果有，则会由局部颜色表的附加数据
         * */
        uint8_t gif_image_descriptor[] = {0x2C, 0x00, 0x00, 0x00, 0x00, 0xBC, 0x02, 0xBC, 0x02, 0x00};
        fwrite(gif_image_descriptor, 10, 1, gif_file);

        //基于颜色表的图像数据 
        /*
         * 已使用LZW算法压缩，解压才是真正的基于颜色表的图像数据
         * 数据的第一个字节表示LZW 编码初始表大小的位数，用于使用 LZW 算法解压数据,后面是图像数据块：
             * 每个数据块第一个字节表示数据块大小（不包括这个字节）
             * 数据块后面的一个字节表示后续数据块大小
             * 当数据块后面的一个字节是`0`，表示数据块结束了
         * 最后一步，我们将使用 LZW 算法解压图像数据块，并根据颜色表还原整张图像（GIF的一帧）的RGB文件,需要删除 编码初始表大小的位数，后面一个数据块大小的数值
         * */
        //一个GIF 文件中可以由多少个图像块，每个图像块就会有图像标识符，描述了当前帧的一些属性
        uint32_t frameSize = gif_width * gif_height;
        uint8_t *gif_one_frame_raw = malloc(frameSize);
        memset(gif_one_frame_raw, i, frameSize);
        printf("当前帧对应的颜色索引：%d\n", gif_one_frame_raw[0]); //如果使用 i 代码的可读性，会更高


        //GIF 一帧图像的数据压缩后大小
        unsigned long compressed_size;
        //GIF 一帧图像的数据解压后的数据
        uint8_t * img = NULL;
        lzw_compress_gif(
                3,
                frameSize,
                gif_one_frame_raw,
                &compressed_size,
                &img
                );
        printf("GIF 一帧图像压缩后大小：%ld\n", compressed_size);
        fputc(0x03, gif_file);
    
        unsigned long current_index = 0;
        while(current_index < compressed_size) {
            if((current_index + 0XFF) >= compressed_size) {
                unsigned long diff = compressed_size - current_index;
                fputc(diff, gif_file);
                fwrite(img + current_index, diff, 1, gif_file);
                fputc(0x00, gif_file); //写入后一个数据块的大小，没有则填充个00
                current_index += diff;
            }else {
                fputc(0xff, gif_file);
                fwrite(img + current_index, 0xFF, 1, gif_file);
                current_index += 0xFF; //填充后一个数据块的大小，因为size 只占据一个字节，所以每次最多写入FF 的数据
            }
        }
        free(gif_one_frame_raw);
        free(img);
    }

    fputc(0x3B, gif_file); //GIF 文件的结尾标志
    fflush(gif_file);
    fclose(gif_file); 


    return 0;
}
